package com.yixing.tech.system.service.impl;

import com.yixing.tech.common.config.SystemSecurityProperties;
import com.yixing.tech.common.security.AuthenticationDetails;
import com.yixing.tech.common.security.AuthenticationUser;
import com.yixing.tech.common.security.UserCertificate;
import com.yixing.tech.common.security.UserDetailsAdapter;
import com.yixing.tech.common.service.AuthenticationDetailsService;
import com.yixing.tech.common.service.SecurityService;
import com.yixing.tech.common.utils.*;
import com.yixing.tech.common.vo.s3.ApplicationVO;
import com.yixing.tech.common.vo.s3.RoleVO;
import com.yixing.tech.common.vo.s3.TenantVO;
import com.yixing.tech.system.po.Application;
import com.yixing.tech.system.po.Role;
import com.yixing.tech.system.po.SystemUser;
import com.yixing.tech.system.po.Tenant;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 授权登录业务实现
 *
 * @author <a href="mailto:brucezhang_jjz@163.com">zhangj</a>
 * @since 1.0.0
 */
@Service
public class SystemSecurityService implements SecurityService {

    private final UserDetailsService userDetailsService;
    private final AuthenticationManager authenticationManager;
    private final SystemSecurityProperties securityProperties;
    private final AuthenticationDetailsService authenticationDetailsService;

    public SystemSecurityService(UserDetailsService userDetailsService, AuthenticationManager authenticationManager,
                                 SystemSecurityProperties securityProperties, AuthenticationDetailsService authenticationDetailsService) {
        this.userDetailsService = userDetailsService;
        this.authenticationManager = authenticationManager;
        this.securityProperties = securityProperties;
        this.authenticationDetailsService = authenticationDetailsService;
    }

    /**
     * 登录用户
     *
     * @param username 用户名
     * @param password 密码
     * @return UserCertificate
     */
    @Override
    public UserCertificate login(String username, String password) {
        //创建Authentication对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username, password);

        //调用AuthenticationManager的authenticate方法进行认证
        Authentication authentication = authenticationManager.authenticate(authenticationToken);

        //登录成功以后用户信息、
        UserDetailsAdapter authUser = (UserDetailsAdapter) authentication.getPrincipal();

        long now = System.currentTimeMillis();
        long accessExpireTime = now + securityProperties.getAccessTokenExpire();
        long refreshExpireTime = now + securityProperties.getRefreshTokenExpire();

        final UserCertificate certificate = getUserCertificate(authUser, authentication);

        // 存储登陆的详细信息（Token等）
        authenticationDetailsService.saveAuthDetails(certificate, accessExpireTime, refreshExpireTime);
        return certificate;
    }

    private static UserCertificate getUserCertificate(UserDetailsAdapter authUser, Authentication authentication) {
        UserCertificate certificate = new UserCertificate();
        certificate.setUserId(authUser.getUserId());
        certificate.setUsername(authUser.getUsername());
        certificate.setToken(TokenUtils.createAccessToken());
        certificate.setRefreshToken(TokenUtils.createAccessToken());
        if(CollectionUtils.isNotEmpty(authentication.getAuthorities())) {
            certificate.setPermissions(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority)
                    .collect(Collectors.joining(",")));
        }
        certificate.setSuperAdmin(authUser.isSuper());

        MongoTemplate mongoTemplate = SpringContextUtil.getBean(MongoTemplate.class);
        SystemUser systemUser = (SystemUser)authUser.getAuthUser();
        String tenantId = systemUser.getTenantId();
        Tenant tenant = mongoTemplate.findOne(MongoUtils.queryId(tenantId), Tenant.class, "tenant");
        if(tenant != null) {
            UserCertificate.Tenant userTenant = new UserCertificate.Tenant();
            userTenant.setId(tenantId);
            userTenant.setCode(tenant.getCode());
            userTenant.setPicUrl(tenant.getPicUrl());
            certificate.setTenant(userTenant);
        }

        List<UserCertificate.Application> userApplications = new ArrayList<>();
        if (certificate.isSuperAdmin()) {
            List<Application> applications = mongoTemplate.find(MongoUtils.simpleQuery("tenantId", tenantId),
                    Application.class, "application");
            if(CollectionUtils.isNotEmpty(applications)) {
                userApplications = applications.stream().map(x -> {
                    UserCertificate.Application app = new UserCertificate.Application();
                    app.setId(x.getId());
                    return app;
                }).collect(Collectors.toList());
            }
        } else {
            Role role = systemUser.getRole();
            if (role != null) {
                role = mongoTemplate.findOne(MongoUtils.queryId(role.getId()), Role.class, "role");
                if(role != null && role.getApplicationIds() != null) {
                    userApplications = role.getApplicationIds().stream().map(x -> {
                        UserCertificate.Application app = new UserCertificate.Application();
                        app.setId(x);
                        return app;
                    }).collect(Collectors.toList());
                }
            }
        }
        certificate.setApplications(userApplications);
        return certificate;
    }

    /**
     * getUserInfo
     */
    @Override
    public AuthenticationUser getUserInfo() {
        return null;
    }

    /**
     * 刷新token
     *
     * @param accessToken  访问令牌
     * @param refreshToken 刷新令牌
     * @return MdCertificate
     */
    @Override
    public UserCertificate refreshToken(String accessToken, String refreshToken) {
        final AuthenticationDetails authDetailsByAccessToken = authenticationDetailsService.getAuthDetailsByAccessToken(accessToken);
        if(authDetailsByAccessToken == null) {
            throw new AccessDeniedException("无授权");
        }
        if(Objects.equals(authDetailsByAccessToken.getRefreshToken(), refreshToken)) {
            authenticationDetailsService.removeAuthDetails(accessToken);
            throw new AccessDeniedException("刷新Token非法");
        }
        if (System.currentTimeMillis() > authDetailsByAccessToken.getRefreshExpireTime()) {
            authenticationDetailsService.removeAuthDetails(accessToken);
            throw new AccessDeniedException("刷新Token过期");
        }

        // 重新生成token
        final UserDetails authUser = userDetailsService.loadUserByUsername(authDetailsByAccessToken.getUsername());
        if(authUser != null) {
            UserCertificate certificate = new UserCertificate();
            certificate.setUsername(authUser.getUsername());
            certificate.setToken(TokenUtils.createAccessToken());
            certificate.setRefreshToken(TokenUtils.createAccessToken());
            if(CollectionUtils.isNotEmpty(authUser.getAuthorities())) {
                certificate.setPermissions(authUser.getAuthorities().stream().map(GrantedAuthority::getAuthority)
                        .collect(Collectors.joining(",")));
            }
            if(ClassUtils.isAssignableFrom(UserDetailsAdapter.class, authUser.getClass())) {
                UserDetailsAdapter uda = (UserDetailsAdapter) authUser;
                certificate.setSuperAdmin(uda.isSuper());
            }
            return certificate;
        } else {
            throw new AccessDeniedException("Token非法");
        }
    }

    /**
     * 用户登出
     *
     * @return Boolean
     */
    @Override
    public boolean logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        AuthenticationDetails details = (AuthenticationDetails) authentication.getDetails();
        return authenticationDetailsService.removeAuthDetails(details.getAccessToken());
    }

}
